"""开票向导"""

import logging
from datetime import datetime

from odoo import models, api, fields
from odoo.tools import float_compare
from odoo.exceptions import UserError, ValidationError
from odoo.addons import decimal_precision as dp
from odoo.tools import groupby
from odoo.tools import float_compare, float_is_zero

_logger = logging.getLogger(__name__)


class PreLine(models.Model):
    _inherit = 'pre.account.line'

    return_reconcile_lines = fields.Many2many('pre.account.line',
                                              'pre_account_line_self_rel',
                                              'this_id',
                                              'return_line_id', string="退货红冲记录")

    @api.multi
    def action_line_draft(self):
        """无需开票状态，转为正常状态，如果有过退货冲抵(return_reconcile_lines不空)，则也需要恢复"""
        free_lines = self.filtered(lambda x: x.state == 'free')
        for line in free_lines:
            if line.return_reconcile_lines:
                # line.state = 'not_invoiced'
                raise UserError(f'此发票明细和{line.return_reconcile_lines.mapped("name")}有退货冲抵的记录，不能从“无需开票”状态恢复。')
            else:
                line.state = 'not_invoiced'

    @api.model
    def record_return_cutting(self, wizard_lines):
        """对于开票前就发生退货的，将红票明细和原发票明细进行对冲。并记录对冲记录。这种操作应该是不可逆转的"""
        reconcile_state = ['not_invoiced', 'partly']
        pre_lines = wizard_lines.mapped('invoice_line')
        if pre_lines.filtered(lambda x: x.state not in reconcile_state):
            raise UserError('所勾选发票明细有处于“已开票”、“红冲”、“无需开票”状态！')
        return_types = ['sale_return', 'purchase_return']
        contrasts = {'sale_return': 'sale', 'purchase_return': 'purchase'}
        # 先区分出退货的红票明细和普通的发票明细
        return_lines = self.env['pre.account.line']
        normal_lines = self.env['pre.account.line']
        for line in pre_lines:
            if line.type in return_types:
                return_lines |= line
            else:
                normal_lines |= line

        # 根据发票明细对应的销售、采购明细来进行匹配红票和对应的正常发票。然后冲抵。
        for return_line in return_lines:
            normal_line = self._get_matched_pre_line(return_line, normal_lines)
            self._reconcile_pre_lines(return_line, normal_line)

    def _reconcile_pre_lines(self, return_line, normal_line):
        """对匹配到的红票明细和普通明细对应对冲：
            1.直接从普通发票明细上减去红票的金额、数量
            2.调整红票状态为“无需开票”
            3.调整普通发票状态，检查对冲后得未开票金额或未开票数量，如果为0，就调整为“已开票”"""
        quantity = normal_line.quantity - return_line.quantity
        precision_digits = 2
        business_amount = normal_line.business_amount - return_line.business_amount
        data = {
            'quantity': quantity,
            'business_untaxed_amount': normal_line.business_untaxed_amount - return_line.business_untaxed_amount,
            'business_tax': normal_line.business_tax - return_line.business_tax,
            'business_amount': business_amount,
            'return_reconcile_lines': [(4, return_line.id)]
        }
        normal_line.write(data)
        # if normal_line.state == 'partly':
        if float_is_zero(normal_line.uninvoiced_amount_tax, precision_digits) or float_is_zero(normal_line.uninvoiced_qty, precision_digits):
            normal_line.state = 'invoiced'
        _logger.info(f'退货发票冲抵成功，正常发票明细：{normal_line.name}，退货发票明细{return_line.name}，数据：{data}')

        return_line.write({'state': 'free', 'return_reconcile_lines': [(4, normal_line.id)]})

    def _get_matched_pre_line(self, return_line, normal_lines):
        """根据单据类型和对应的销售、采购明细来为红票明细匹配正常发票"""
        if return_line.type == 'sale_return':
            if return_line.sale_line_id:
                normal_sale_lines = normal_lines.filtered(lambda x: x.sale_line_id == return_line.sale_line_id)
                normal_line = self._get_matched_lines(return_line, normal_sale_lines)
            else:
                raise UserError('退货红票明细没有的销售明细！')
        elif return_line.type == 'purchase_return':
            if return_line.purchase_line_id:
                normal_purchase_lines = normal_lines.filtered(lambda x: x.purchase_line_id == return_line.purchase_line_id)
                normal_line = self._get_matched_lines(return_line, normal_purchase_lines)
            else:
                raise UserError(f'退货红票明细{return_line.name}里没有的采购明细！')
        else:
            raise UserError(f'不能识别的发票明细类型：{return_line.type}')
        return normal_line

    def _get_matched_lines(self, return_line, normal_lines):
        """
        检查匹配到的发票明细，检查剩余数量、剩余金额等。
        :param return_line: 红票明细
        :param normal_lines: 和红票明细具有相同业务单据的正常发票明细
        :return: 数量、金额等都符合条件的正常发票明细
        """
        round_precision = 2
        check_fields = {'quantity': 'uninvoiced_qty',
                        'business_amount': 'uninvoiced_amount_tax'}
        matched_line = False
        for normal_line in normal_lines:
            for return_field, nornal_field in check_fields.items():
                if float_compare(getattr(return_line, return_field),
                                 getattr(normal_line, nornal_field),
                                 round_precision) != 1:
                    matched_line = normal_line
                    break
        if not matched_line:
            raise UserError(f'红冲明细：{return_line.name}没有找到对应的原发票明细，请检查勾选的内容')
        return matched_line


class InvoiceWizard(models.TransientModel):
    _name = 'nd.invoice.wizard'
    _description = '开票向导'

    hc_option = fields.Selection([
        ('all', '整张红冲'),
        ('part', '部分红冲'),
        ('reconcile', '退货冲抵'),
        ('free', '无需开票'),
        ], string="红冲选项")
    reason = fields.Char('红冲原因')
    available_invoice_ids = fields.Many2many('account.invoice', string="允许勾选的发票")
    # invoice_id = fields.Many2one('account.invoice', string="要红冲的发票")
    invoice_ids = fields.Many2many('account.invoice', string="要红冲的发票")
    line_ids = fields.One2many('nd.invoice.wizard.line', 'wizard_id', string="明细")
    has_hc = fields.Boolean('是否有红冲', default=False)

    @api.multi
    def confirm(self):
        self.ensure_one()
        invoice_id = self.env['account.invoice']
        if self.hc_option == 'free':    # 标记为无需开票，普通、红冲明细均可
            self.line_ids.mapped('invoice_line').mark_line_free()
        elif self.hc_option == 'reconcile':  # 开票前退货，将退货数量和金额从原发票明细上扣减
            self.env['pre.account.line'].record_return_cutting(self.line_ids)
        elif self.hc_option in ['all', 'part']:     # 红冲，没有正常明细。利用系统的信用票，生成一张红冲发票，
            invoice_id = self.env['account.invoice']._nd_generate_hongchong_invoice(self)
        else:                           # 正常开票，没有红冲明细
            invoice_id = self.env['account.invoice'].nd_generate_account_invoice(self)

        if invoice_id:
            # 没有计算税金，这里手动计算
            self._update_tax_info(invoice_id)
            invoice_id = invoice_id[0]
            if invoice_id.type in ['out_invoice', 'out_refund']:
                action = self.env.ref('account.action_invoice_tree1').read()[0]
            elif invoice_id.type in ['in_invoice', 'in_refund']:
                action = self.env.ref('account.action_vendor_bill_template').read()[0]
            else:
                raise UserError('不能识别的发票类型')
            action['res_id'] = invoice_id.id
            action['views'] = [(False, 'form')]
            return action

    def _update_tax_info(self, invoice_ids):
        """这里手动添加tax_line_ids来触发税金的重新计算，默认生成的发票没有税金信息
        """
        for invoice_id in invoice_ids:
            taxes_grouped = invoice_id.get_taxes_values()
            tax_lines = invoice_id.tax_line_ids.filtered('manual')
            for tax in taxes_grouped.values():
                tax_lines += tax_lines.new(tax)
            invoice_id.tax_line_ids = tax_lines

    @api.model
    def nd_invoice_wizard(self, pre_invoice_line_ids):
        """开始开票向导"""
        pre_invoice_lines = self.env['pre.account.line'].browse(pre_invoice_line_ids)
        self.env['account.invoice']._nd_check_pre_invoice_lines(pre_invoice_lines)
        action = self.env.ref('nd_account.NDInvoiceWizardAction').read()[0]

        datas = []
        for line in pre_invoice_lines:
            uninvoiced_amount = max(line.uninvoiced_amount_tax, 0)
            data = {
                'invoice_line': line.id,
                'is_return': line.type in ['sale_return', 'purchase_return'],
                'left_amount': uninvoiced_amount,
                'left_qty': line.quantity - line.invoiced_qty,
                'invoice_amount': uninvoiced_amount,
                'invoice_qty': line.quantity - line.invoiced_qty,
            }
            datas.append((0, 0, data))

        types = pre_invoice_lines.mapped('type')
        has_return = 'sale_return' in types or 'purchase_return' in types   # 标记是否有红冲明细

        wizard = self.env['nd.invoice.wizard'].create({
            'has_hc': has_return,
            'available_invoice_ids': [(6, 0, self._get_invoices_relative(pre_invoice_lines).ids)],
            'line_ids': datas
        })

        action['res_id'] = wizard.id
        return action

    @api.model
    def _get_invoices_relative(self, pre_invoice_line_ids):
        """获取指定发票明细相关的发票"""
        sale_orders = pre_invoice_line_ids.mapped('sale_line_id.order_id')
        purchase_orders = pre_invoice_line_ids.mapped('purchase_line_id.order_id')
        invoices = self.env['account.invoice']
        invoices |= sale_orders.mapped('order_line.invoice_lines.invoice_id')
        invoices |= purchase_orders.mapped('order_line.invoice_lines.invoice_id')
        return invoices.filtered(lambda x: x.type in ['out_invoice', 'in_invoice'] and not x.nd_total_hc)   # 避开红冲发票


class InvoiceWizardLine(models.TransientModel):
    _name = 'nd.invoice.wizard.line'
    _description = '开票向导明细'

    wizard_id = fields.Many2one('nd.invoice.wizard')
    invoice_line = fields.Many2one('pre.account.line', string="发票明细")
    is_return = fields.Boolean('是否红冲明细')
    left_amount = fields.Float('未开票金额')
    left_qty = fields.Float('未开票数量')
    invoice_amount = fields.Float('本次开票金额')
    invoice_qty = fields.Float('本次开票数量')
    finish = fields.Boolean('已全部开票', default=True)


